35. Instead of Virtual Function

파생 클래스에서 오버라이딩 할 가능성이 있는 함수에 대하여서 virtual function으로 정의한다.
class GameCharacter{
public:
virtual int healthValue() const; // ;
// ...
};
가상 함수 대신 사용할 수 있는 방법
- 비가상 인터페이스 관용구를 통한 템플릿 메서드 패턴
- 함수 포인터로 구현한 전략 패턴
- tr1::function으로 구현한 전략 패턴
- 고전적인 전략 패턴
비가상 인터페이스 관용구를 통한 템플릿 메서드 패턴
일부 개발자들을 가상함수를 private 멤버로 둘 것을 주장한다.(가상 함수 은폐론)
class GameCharacter{
public:
int healthValue() const{
// ...
int retVal=doHealthValue();
// ...
return retVal;
}
// ...
private:
virtual int doHealthValue() const{
// act
}
};
위와 같이 public 비가상 멤버 함수(healthValue)를 통해 private 가상함수를 간접적으로 호출하게 만든다.
비가상 함수 인터페이스(non-virtual interface: NVI) 관용구

사전 동작에 자원을 할당하고, 사후 동작에 할당받은 자원을 반환한다.

위와 같이 구현하면, 가상 함수의 재정의를 어떻게 할지는 파생 클래스에 맡기지만,
언제 가상 함수가 호출되는지는 기본 클래스의 고유 권한이다.
함수 포인터로 구현한 전략 패턴
포인터로 함수의 포인터를 넘기고 이를 호출해서 사용
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
// int const GameCharacter&
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc): healthFunc(hcf) {}
int healthValue() const{ return healthFunc(*this); }
// ...
private:
HealthCalcFunc healthFunc;
};
class EvilBadGuy: public GameCharacter{
public:
explicit EvilBadGuy(HealthCalcFunc hcf=defaultHealthCalc): GameCharacter(hcf){ /* ... */ }
// ...
};
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy edg2(loseHealthSlowly);
위와 같이 구현하면, 동일한 타입으로 생성된 객체에 대해
서로 다른 함수를 가질 수 있다.(다른 함수를 포인팅 시켜 주면 된다.)

하지만, 함수를 외부에 구현해야 하기 때문에 객체의 private 혹은 protected 영역에 정의된 멤버에 접근할 수 없다.
(접근 시킬려고 friend 혹은 접근 함수(public으로)를 제공하면, 캡슐화를 약화시킨다.)
tr1::function으로 구현한 전략 패턴
tr1::function 타입의 객체는 함수 호출성 개체(callable entity)를 가질 수 있고,
이들 개체는 주어진 시점에서 예상되는 시그니처와 호환되는 시그너처를 갖는다.

아래에서 HealthCalcFunc는 함수 호출성 객체로, GameCharacter와 호환되는 모든 것을 넘겨받을 수 있으며,
int와 호환되는 모든 타입의 객체를 반환한다.
- 일반화된 함수 포인터 타입처럼 동작
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc): healthFunc(hcf) {}
int healthValue() const{ return healthFunc(*this); }
// ...
private:
HealthCalcFunc healthFunc;
};
short calcHealth(const GameCharacter&);
struct HealthCalculator{
int operator()(const GameCharacetr&) const{
// ...
}
};
class GameLevel{
public:
float health(const GameCharacter&) const;
// ...
};
class EvilBadGuy: public GameCharacter{
// ...
};
class EyeCandyCharacter: public GameCharacter{
// ...
};
EvilBadGuy ebg1(calcHealth); //
EyeCandyCharacter ecc1(HealthCalculator()); //
GameLevel currentLevel;
// ...
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::helth, currentLevel, _1));
고전적인 전략 패턴
class GameCharacter;
class HealthCalcFunc{
public:
// ...
virtual int calc(const GameCharacter& gc) const{
// ...
}
// ...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter{
public:
explicit GameCharacter(HealthCalcFunc* phcf=&defaultHealthCalc): pHealthCalc(phcf) {}
int healthValue() const{
return pHealthCalc->calc(*this);
}
// ...
private:
HealthCalcFunc* pHealthCalc;
}